﻿using Cobalt;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;

namespace WopiCobaltHost
{
    public class CobaltServer
    {
        private HttpListener m_listener;
        private string m_docsPath;
        private string m_host;
        private int m_port;

        public CobaltServer(string docsPath = "", string host = "localhost", string port = "8080")
        {
            if (string.IsNullOrEmpty(docsPath))
            {
                docsPath = System.IO.Path.Combine(System.Environment.CurrentDirectory, "files");
            }
            m_docsPath = docsPath;


            if (string.IsNullOrEmpty(host))
            {
                host = "localhost";
            }
            m_host = host;

            if (!int.TryParse(port, out m_port))
            {
                m_port = 8080;
            }
        }

        public void Start()
        {
            string url = String.Format("http://" + m_host + ":{0}/wopi/", m_port);
            Console.WriteLine(url);
            m_listener = new HttpListener();
            m_listener.Prefixes.Add(url);
            m_listener.Start();
            m_listener.BeginGetContext(ProcessRequest, m_listener);

            Console.WriteLine(@"WopiServer Started");

            Console.Title = url;
        }

        public void Stop()
        {
            m_listener.Stop();
        }

        private void ErrorResponse(HttpListenerContext context, string errmsg)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(errmsg);
            context.Response.ContentLength64 = buffer.Length;
            context.Response.ContentType = @"application/json";
            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
            context.Response.OutputStream.Close();
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            context.Response.Close();
        }

        private void ProcessRequest(IAsyncResult result)
        {
            try
            {
                Console.WriteLine();
                Console.WriteLine(DateTime.Now.ToString());

                Console.WriteLine("start...");
                HttpListener listener = (HttpListener)result.AsyncState;
                HttpListenerContext context = listener.EndGetContext(result);
                try
                {
                    Console.Write("1111...  ");
                    Console.WriteLine(context.Request.HttpMethod + @" " + context.Request.Url.AbsolutePath);

                    var stringarr = context.Request.Url.AbsolutePath.Split('/');
                    var access_token = context.Request.QueryString["access_token"];

                    if (stringarr.Length < 3 || access_token == null)
                    {
                        Console.WriteLine(@"Invalid request");
                        ErrorResponse(context, @"Invalid request parameter");
                        m_listener.BeginGetContext(ProcessRequest, m_listener);
                        return;
                    }


                    var filename = stringarr[3];



                    //use filename as session id just test, recommend use file id and lock id as session id
                    EditSession editSession = CobaltSessionManager.Instance.GetSession(filename);
                    //if (editSession != null)
                    //{
                    //    Console.WriteLine("~~~~~~~~~~~~~~~~~~~...      " + filename);
                    //    CobaltSessionManager.Instance.DelSession(editSession);
                    //    editSession = null;
                    //}

                    if (editSession == null)
                    {
                        Console.Write("2222...      ");
                        var fileExt = filename.Substring(filename.LastIndexOf('.') + 1).ToLower();// 没有包含 扩展名前的【.】

                        string filePath = "";
                        if (filename.Length > 36 && filename.Substring(0, 4) != "test")// guid长度为36
                        {
                            filePath = System.IO.Path.Combine(m_docsPath,
                                fileExt,
                                filename.Substring(0, 2),
                                filename.Substring(2, 2),
                                filename.Substring(4, 2),
                                filename);
                        }
                        else
                        {
                            filePath = System.IO.Path.Combine(m_docsPath, filename);
                        }
                        Console.Write("【" + System.IO.File.Exists(filePath) + "】");
                        Console.WriteLine(filePath);

                        if (fileExt.Equals(@"xlsx"))
                            editSession = new FileSession(filename, filePath, @"chaoshuai.lu", @"RDMP", @"lcs@rdmp.com.cn", false);
                        else
                            editSession = new CobaltSession(filename, filePath, @"chaoshuai.lu", @"RDMP", @"lcs@rdmp.com.cn", false);
                        CobaltSessionManager.Instance.AddSession(editSession);
                    }

                    if (stringarr.Length == 4 && context.Request.HttpMethod.Equals(@"GET"))
                    {
                        Console.WriteLine("4444...  GetCheckFileInfo");
                        //request of checkfileinfo, will be called first
                        var memoryStream = new MemoryStream();
                        var json = new DataContractJsonSerializer(typeof(WopiCheckFileInfo));
                        json.WriteObject(memoryStream, editSession.GetCheckFileInfo());
                        memoryStream.Flush();
                        memoryStream.Position = 0;
                        StreamReader streamReader = new StreamReader(memoryStream);
                        var jsonResponse = Encoding.UTF8.GetBytes(streamReader.ReadToEnd());

                        context.Response.ContentType = @"application/json";
                        context.Response.ContentLength64 = jsonResponse.Length;
                        context.Response.OutputStream.Write(jsonResponse, 0, jsonResponse.Length);
                        context.Response.Close();
                    }
                    else if (stringarr.Length == 5 && stringarr[4].Equals(@"contents"))
                    {
                        Console.WriteLine("5555...  get and put file's content, only for xlsx and pptx");
                        // get and put file's content, only for xlsx and pptx
                        if (context.Request.HttpMethod.Equals(@"POST"))
                        {
                            Console.Write("    ...  POST----save fiies !  ");

                            var ms = new MemoryStream();
                            context.Request.InputStream.CopyTo(ms);
                            Console.WriteLine("SaveArray.Length: " + ms.Length);
                            editSession.Save(ms.ToArray());
                            context.Response.ContentLength64 = 0;
                            context.Response.ContentType = @"text/html";
                            context.Response.StatusCode = (int)HttpStatusCode.OK;
                        }
                        else
                        {
                            Console.Write("    ...  GET----read fiies !  ");
                            var content = editSession.GetFileContent();
                            Console.WriteLine("content.Length: " + content.Length);


                            context.Response.ContentType = @"application/octet-stream";
                            context.Response.ContentLength64 = content.Length;
                            context.Response.OutputStream.Write(content, 0, content.Length);
                        }
                        context.Response.Close();
                    }
                    else if (context.Request.HttpMethod.Equals(@"POST") &&
                        context.Request.Headers["X-WOPI-Override"].Equals("COBALT"))
                    {
                        Console.WriteLine("6666...  cobalt, for docx and pptx");
                        //cobalt, for docx and pptx
                        var ms = new MemoryStream();
                        context.Request.InputStream.CopyTo(ms);

                        Console.WriteLine("    ...  save ! length: " + ms.Length);
                        AtomFromByteArray atomRequest = new AtomFromByteArray(ms.ToArray());
                        RequestBatch requestBatch = new RequestBatch();

                        Object ctx;
                        ProtocolVersion protocolVersion;

                        requestBatch.DeserializeInputFromProtocol(atomRequest, out ctx, out protocolVersion);
                        editSession.ExecuteRequestBatch(requestBatch);

                        foreach (Request request in requestBatch.Requests)
                        {
                            if (request.GetType() == typeof(PutChangesRequest) &&
                                request.PartitionId == FilePartitionId.Content)
                            {

                                editSession.Save();
                            }
                        }
                        var response = requestBatch.SerializeOutputToProtocol(protocolVersion);

                        context.Response.Headers.Add("X-WOPI-CorellationID", context.Request.Headers["X-WOPI-CorrelationID"]);
                        context.Response.Headers.Add("request-id", context.Request.Headers["X-WOPI-CorrelationID"]);
                        context.Response.ContentType = @"application/octet-stream";
                        context.Response.ContentLength64 = response.Length;
                        response.CopyTo(context.Response.OutputStream);
                        context.Response.Close();
                    }
                    else if (context.Request.HttpMethod.Equals(@"POST") &&
                        (context.Request.Headers["X-WOPI-Override"].Equals("LOCK") ||
                        context.Request.Headers["X-WOPI-Override"].Equals("UNLOCK") ||
                        context.Request.Headers["X-WOPI-Override"].Equals("REFRESH_LOCK"))
                        )
                    {
                        Console.WriteLine("7777...  lock, for xlsx and pptx");
                        //lock, for xlsx and pptx
                        context.Response.ContentLength64 = 0;
                        context.Response.ContentType = @"text/html";
                        context.Response.StatusCode = (int)HttpStatusCode.OK;
                        context.Response.Close();
                    }
                    else
                    {
                        Console.WriteLine();
                        Console.WriteLine();
                        Console.WriteLine(@"Invalid request parameters");
                        ErrorResponse(context, @"Invalid request cobalt parameter");
                    }

                    Console.WriteLine("ok...");
                    Console.WriteLine();
                    Console.WriteLine();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(@"process request exception:" + ex.Message);
                }
                m_listener.BeginGetContext(ProcessRequest, m_listener);
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"get request context:" + ex.Message);
                return;
            }
        }

    }
}
